package jp.tkym.labs.store.sql;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;


import jp.tkym.labs.store.ormap.OrMapElement;
import jp.tkym.labs.store.ormap.OrMapObject;
import jp.tkym.labs.store.ormap.OrMapObjectProvider;
import jp.tkym.labs.util.beankey.BeanKey;
import jp.tkym.labs.util.beankey.BeanKeyGenerator;
import jp.tkym.labs.util.prop.PropertyResolver;
import jp.tkym.labs.util.prop.PropertyResolverProvider;

public class PreparedStatementQuery<X>{
	private PropertyResolver<X> resolver;
	private List<String> bindProperties;
	private PreparedStatementBindHelper helper;
	private ResultSet rs = null;
	private PreparedStatement ps;
	private BeanKeyGenerator<X> keyGenerator;
	private Map<String , String > orMap;
	
	PreparedStatementQuery(Class<X> cls, PreparedStatement ps, String... bindProperties) {
		helper = new PreparedStatementBindHelper();
		resolver = PropertyResolverProvider.getInstance().get(cls);
		if(bindProperties.length == 0){
			this.bindProperties = resolver.propertyNames();
		}else{
			this.bindProperties = Arrays.asList(bindProperties);
		}
		this.ps = ps;
		OrMapObject ormapper = OrMapObjectProvider.getInstance().get(cls);
		String[] keyPropeties = ormapper.keyProperties();
		this.keyGenerator = new BeanKeyGenerator<X>(cls, keyPropeties);
		orMap = new HashMap<String, String>();
		for(OrMapElement element : ormapper.elements()){
			orMap.put(element.getPropertyName(), element.getColumnName());
		}
	}
	
	public void execute(){
		try {
			this.rs = ps.executeQuery();
		} catch (SQLException e) {
			throw new PreparedStatementException(e);
		}
	}
	
	public List<X> asList() throws SQLException{
		List<X> resultList = new ArrayList<X>();
		while(rs.next())resultList.add(toBean(rs));
		return resultList;
	}
	
	public Set<BeanKey> asKeySet() throws SQLException{
		Set<BeanKey> keySet = new HashSet<BeanKey>();
		while(rs.next())keySet.add(keyGenerator.generate(toBean(rs)));
		return keySet;
	}
	
	X next() throws SQLException{
		if(rs.next()) return toBean(rs);
		return null;
	}
	
	public Iterator<X> asIterator()throws SQLException{
		return new ResultSetIterator<X>(this);
	}
	
	class ResultSetIterator<E> implements Iterator<E>{
		private PreparedStatementQuery<E> query;
		private E next = null;
		private boolean hasNext;
		
		ResultSetIterator(PreparedStatementQuery<E> query){
			this.query = query;
			nextCursol();
		}
		
		private void nextCursol(){
			try {
				next = query.next();
			} catch (SQLException e) {
				throw new PreparedStatementException(e);
			}
			if(next != null){
				hasNext = true;
			}else{
				hasNext = false;
			}
		}
		
		@Override
		public boolean hasNext() {
			return hasNext;
		}

		@Override
		public E next() {
			E pre = next;
			nextCursol();
			return pre;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
	
	private X toBean(ResultSet rs) throws SQLException{
		X bean = resolver.createInstance();
		for(String propertyName : bindProperties){
			Object value = value(propertyName, rs);
			resolver.set(bean, propertyName, value);
		}
		return bean;
	}
	
	private Object value(String columnName, ResultSet rs) throws SQLException{
		Class<?> cls = resolver.getTypeOf(columnName);
		return helper.get(orMap.get(columnName), cls, rs);
	}
}